from typing import Any
import json

import threadpool
from entry.ICar import ICar
from store.IStoreService import IStoreService
from utils.CarHttp import CarHttp
from utils.CarUtil import CarUtil
from utils.LogUtil import LogUtil

car: ICar = None
mode: str = None
http: CarHttp = None
storeService: IStoreService = None
logger: LogUtil = LogUtil


# 车型配置抓取任务单元
def ConfigPageCrawler(id, series_id, *args, **kwargs):
  global _car, _mode, _http, _storeService, _logger

  _logger.info(f'正在获取 车型系列号[{series_id}]')

  # 获取系列车型的数据
  path = '/auto/series/{}'.format(series_id)
  res = _http.get(path=path)
  if not res or res.status_code != 200:
    _logger.warn(
        f'[{_http.combineUrl(path)}] - 获取系列车型页面失败,错误码：{res.status_code}',
    )
    return

  # 提取页面里的 json 数据
  jsonObj = getJsonObj(res.text)
  # 系列车型数据
  try:
    car_infos = jsonObj['props']['pageProps']['overviewData']['car_info']
  except Exception as ex:
    _logger.error(ex, "获取系列车型数据失败")
    return

  for car_item in car_infos:
    car_id = car_item.get('car_Id')
    series_name = car_item.get('series_name')
    car_name = car_item.get('car_name')
    _logger.info(f'正在爬取 [{series_name}] - [{car_name}] 的配置数据')

    # 获取配置数据
    path = '/auto/series/{}/model-{}'.format(series_id, car_id)
    res = _http.get(path)
    if not res or res.status_code != 200:
      _logger.warn(
          f'[{_http.combineUrl(path)}]-获取车型配置页面失败,错误码:{str(res) if not res else res.status_code}'
      )
      continue

    jsonObj = getJsonObj(res.text)
    try:
      # 保存
      data = resolveConfigData(jsonObj, _http.combineUrl(path))
      data = ( data, id )
      if _mode != 'TEST':
        _storeService.insert_info(data)
    except Exception as ex:
      _logger.error(ex, "车型信息为空")
      # if car.d_print('car_info为空', car_info):
      # traceback.print_exc()
      # return

    if _mode == 'DEBUG':
      return


@CarUtil.timmer
def start(car: ICar, *args, **kwargs):
  global _car, _mode, _http, _storeService, _logger

  _car = car
  _mode = car.mode
  _http = car.http
  _storeService = car.storeService
  _logger = LogUtil

  # 根据任务需要开启线程池来执行车型数据爬取
  # 线程数量
  work_thread_num = _car.config.get('WorkThreadNum', 1)
  # 测试数量
  test_num: int = car.config.get('TestWorkNum', 10)

  # 车型系列数据
  series = None
  if _mode == "TEST":
    series = _storeService.get_series(test_num)
  else:
    series = _storeService.get_series()

  # 打印日志
  if not series and len(series) < 1:
    _logger.warn("没有要爬取的车型系列")

  # 拼接成任务参数
  task_params = [((item), None) for item in series]

  if _mode == "PRODUCT":
    # 线程池
    pool = threadpool.ThreadPool(work_thread_num)
    # 初始化线程池
    threads = threadpool.makeRequests(ConfigPageCrawler, task_params)
    for thread_item in threads:
      pool.putRequest(thread_item)
    # 阻塞主线程等待线程池任务完成
    pool.wait()
  #endif
  elif _mode == "DEBUG":
    # 线程池
    for params in task_params:
      ConfigPageCrawler(params)
  #endif
  elif _mode == "TEST":
    _logger.info(f'线程数量: {work_thread_num}')
    _logger.info(f'测试数量: {len(task_params)}')
    # 线程池
    pool = threadpool.ThreadPool(work_thread_num)
    threads = threadpool.makeRequests(ConfigPageCrawler, task_params)
    for thread_item in threads:
      pool.putRequest(thread_item)
    pool.wait()
  #endif


# 获取页面的json数据
def getJsonObj(text: str) -> Any | None:
  """获取页面的 json 数据

  Args:
      text (str): json 字符串文本

  Returns:
      Any | None: json 对象或为空
  """

  pattern = '<script id="__NEXT_DATA__" type="application/json" crossorigin="anonymous">(.*?)</script>'
  jsonStr = CarUtil.extractByRe(text, pattern, 1)
  jsonDict = json.loads(jsonStr)
  return jsonDict


# 获取车的配置数据
def resolveConfigData(jsonObj: Any | None, series_url: str) -> dict:
  """获取车的配置数据

  Args:
      jsonObj (Any | None): json 对象
      series_url (str): 车型链接

  Returns:
      dict: 返回字典对象
  """

  root = jsonObj['props']['pageProps']
  carHead = root['carHead']
  sub_brand_id = carHead['sub_brand_id']
  sub_brand_name = carHead['sub_brand_name']
  series_url = series_url
  series_id = carHead['series_id']
  series_name = carHead['series_id']
  img_url = carHead['img_url']
  year = carHead['year']
  car_id = carHead['car_id']
  car_name = carHead['car_name']
  official_price = carHead['official_price']
  # 获取配置
  core_config = carHead['core_config']                   # core_config
  engine_description = core_config['engine_description'] # 发动机
  acceleration_time = core_config['acceleration_time']   # 百公里加速
  fuel_comprehensive = core_config['fuel_comprehensive'] # 百公里油耗
  front_suspension_form_description = core_config[
      'front_suspension_form_description']
  rear_suspension_form_description = core_config[
      'rear_suspension_form_description']
  seat_material = core_config['seat_material']
  oil_tank_volume = core_config['oil_tank_volume']
  engine_max_power = core_config['engine_max_power']
  fuel_form_description = core_config['fuel_form_description']
  driven_form_description = core_config['driven_form_description']
  wheelbase = core_config['wheelbase']
  max_speed = core_config['max_speed']
  engine_max_torque = core_config['engine_max_torque']
  gearbox_description = core_config['gearbox_description']
  fourwheel_drive_type_description = core_config[
      'fourwheel_drive_type_description']
  air_control_model = core_config['air_control_model']
  curb_weight = core_config['curb_weight']
  seat_count = core_config['seat_count']                 # 座位
  baggage_volume = core_config['baggage_volume']         # 行李舱容积(L)

  data = {
      'sub_brand_id': sub_brand_id,
      'sub_brand_name': sub_brand_name,
      'series_url': series_url,
      'series_id': series_id,
      'series_name': series_name,
      'img_url': img_url,
      'year': year,
      'car_id': car_id,
      'car_name': car_name,
      'official_price': official_price,
      'engine_description': engine_description,
      'acceleration_time': acceleration_time,
      'fuel_comprehensive': fuel_comprehensive,
      'front_suspension_form_description': front_suspension_form_description,
      'rear_suspension_form_description': rear_suspension_form_description,
      'seat_material': seat_material,
      'oil_tank_volume': oil_tank_volume,
      'engine_max_power': engine_max_power,
      'fuel_form_description': fuel_form_description,
      'driven_form_description': driven_form_description,
      'wheelbase': wheelbase,
      'max_speed': max_speed,
      'engine_max_torque': engine_max_torque,
      'gearbox_description': gearbox_description,
      'fourwheel_drive_type_description': fourwheel_drive_type_description,
      'air_control_model': air_control_model,
      'curb_weight': curb_weight,
      'seat_count': seat_count,
      'baggage_volume': baggage_volume
  }

  return data
